﻿using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Text;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace CodeAnalysisRuleCollection.Common.CSharp
{
    /// <example>if (...) a = 2; else a = 3;</example>
    [ExportCodeRefactoringProvider("CompactConditionalAssignment", LanguageNames.CSharp)]
    internal partial class CompactConditionalAssignmentCodeRefactoringProvider : ICodeRefactoringProvider
    {
        public async Task<IEnumerable<CodeAction>> GetRefactoringsAsync(Document document, TextSpan span, CancellationToken cancellationToken)
        {
            var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            var @if = root.FindToken(span.Start).Parent.FirstAncestorOrSelf<IfStatementSyntax>();
            if (@if == null || @if.Else == null)
                return null;

            ExpressionSyntax assignmentTarget = null;

            // TODO: Handle not only assignments

            if (IsCollapsible(@if, out assignmentTarget, cancellationToken))
                return new[] { CodeAction.Create("Convert if statement to conditional expression", c => ConvertIfToConditional(root, document, @if, assignmentTarget, c)) };

            return null;
        }

        protected virtual bool IsCollapsible(IfStatementSyntax @if, out ExpressionSyntax assignmentTarget, CancellationToken c)
        {
            assignmentTarget = null;

            // when true
            ExpressionStatementSyntax whenTrueStatement = GetEffectiveExpression(@if.Statement);
            if (whenTrueStatement == null)
                return false;

            BinaryExpressionSyntax whenTrueBinary = whenTrueStatement.Expression as BinaryExpressionSyntax;
            if (
                whenTrueBinary == null ||
                !whenTrueBinary.IsKind(SyntaxKind.SimpleAssignmentExpression)
            )
                return false;

            // when false
            ExpressionStatementSyntax whenFalseStatement = GetEffectiveExpression(@if.Else.Statement);
            if (whenFalseStatement == null)
                return false;

            BinaryExpressionSyntax whenFalseBinary = whenFalseStatement.Expression as BinaryExpressionSyntax;
            if (
                whenFalseBinary == null ||
                !whenFalseBinary.IsKind(SyntaxKind.SimpleAssignmentExpression)
            )
                return false;

            // match experssions
            if (!SyntaxFactory.AreEquivalent(whenTrueBinary.Left, whenFalseBinary.Left))
                return false;
            
            assignmentTarget = whenTrueBinary.Left;
            return true;
        }

        protected virtual async Task<Document> ConvertIfToConditional(SyntaxNode root, Document document, IfStatementSyntax @if, ExpressionSyntax assignmentTarget, CancellationToken c)
        {
            var newRoot = root
                .ReplaceNode<SyntaxNode, StatementSyntax>(
                    @if,
                    SyntaxFactory.ExpressionStatement(
                        SyntaxFactory.BinaryExpression(SyntaxKind.SimpleAssignmentExpression,
                            assignmentTarget,
                            SyntaxFactory.ConditionalExpression(@if.Condition,
                                (GetEffectiveExpression(@if.Statement).Expression as BinaryExpressionSyntax).Right,
                                (GetEffectiveExpression(@if.Else.Statement).Expression as BinaryExpressionSyntax).Right
                            )
                        )
                    )
                        .WithAdditionalAnnotations(Formatter.Annotation)
                )
            ;

            return document.WithSyntaxRoot(newRoot);
        }

        protected static ExpressionStatementSyntax GetEffectiveExpression(StatementSyntax statement)
        {
            if ((var expr = statement as ExpressionStatementSyntax) != null)
                return expr;

            if ((var block = statement as BlockSyntax) != null)
            if ((var single = block.GetEffectiveStatement()) != null)
                return single as ExpressionStatementSyntax;

            return null;
        }
    }
}
